package com.fsy.its.netty;

import com.fsy.its.netty.config.GbtEncoder;
import com.fsy.its.netty.dtu.DtuIdDecoder;
import com.fsy.its.netty.gbt.decoder.GbtFrameDecoder;
import com.fsy.its.netty.gbt.decoder.GbtMessageDecoder;
import com.fsy.its.netty.gbt.encoder.GbtMessageEncoder;
import com.fsy.its.netty.handler.ItsMessageHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * 服务端
 * 1.创建一个ServerBootstrap的实例引导和绑定服务器。
 * 2.创建并分配一个NioEventLoopGroup实例以进行事件的处理，比如接受连接以及读写数据。
 * 3.指定服务器绑定的本地的InetSocketAddress。
 * 4.使用一个EchoServerHandler的实例初始化每一个新的Channel。
 * 5.调用ServerBootstrap.bind()方法以绑定服务器。
 */
@Slf4j
@Component
public class NettyServer {

    //DTU透传端口
    @Value("${netty.dtu.port}")
    private int dtuPort;

    //国标协议端口
    @Value("${netty.gbt.port}")
    private int gbtPort;

    /**
     * NioEventLoop并不是一个纯粹的I/O线程，它除了负责I/O的读写之外
     * 创建了两个NioEventLoopGroup，
     * 它们实际是两个独立的Reactor线程池。
     * 一个用于接收客户端的TCP连接，
     * 另一个用于处理I/O相关的读写操作，或者执行系统Task、定时任务Task等。
     */
    private final EventLoopGroup bossGroup = new NioEventLoopGroup();
    private final EventLoopGroup workerGroup = new NioEventLoopGroup();
    private Channel dtuChannel;
    private Channel gbtChannel;

    @Autowired
    private ApplicationContext applicationContext;


    /**
     * 启动服务
     * @return
     * @throws Exception
     */
    public ChannelFuture[] start() {
        ChannelFuture[] futures = new ChannelFuture[2];
        try {
            //ServerBootstrap负责初始化netty服务器，并且开始监听端口的socket请求
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            onChannelInit(socketChannel);
                        }
                    });

            futures[0] = bootstrap.bind(dtuPort).sync();
            dtuChannel = futures[0].channel();

            futures[1] = bootstrap.bind(gbtPort).sync();
            gbtChannel = futures[1].channel();
        }
        catch (Exception e) {
            log.error("Start netty exception: ", e);
        }
        finally {
            if (futures[0] != null && futures[1]!=null
                    && futures[0].isSuccess() && futures[1].isSuccess()) {
                log.info("Netty server start up successfully, dtuPort={}, gbtPort={}", dtuPort, gbtPort);
            } else {
                log.error("Netty server start up Error!");
            }
        }
        return futures;
    }

    private void onChannelInit(SocketChannel socketChannel) {
        int port=socketChannel.localAddress().getPort();
        if( port == dtuPort ) {
            socketChannel.pipeline().addLast("dtuIdDecoder", applicationContext.getBean(DtuIdDecoder.class));
            socketChannel.pipeline().addLast("handler", applicationContext.getBean(ItsMessageHandler.class));
            log.info("A dtu client connected: {}", socketChannel.remoteAddress());
        }
        else if( port == gbtPort ) {
            socketChannel.pipeline().addLast(applicationContext.getBean(GbtFrameDecoder.class));
            socketChannel.pipeline().addLast(applicationContext.getBean(GbtMessageDecoder.class));

            Map<String, Object> encoders = applicationContext.getBeansWithAnnotation(GbtEncoder.class);
            for( Map.Entry<String, Object> entry : encoders.entrySet() ) {
                Object encoder = entry.getValue();
                if( !(encoder instanceof GbtMessageEncoder) ) {
                    log.error("Gbt encoder {} not an instance of GbtMessageEncoder", encoder.getClass().getName());
                    System.exit(-1);
                    return;
                }
                socketChannel.pipeline().addLast((ChannelHandler) encoder);
            }

            socketChannel.pipeline().addLast(applicationContext.getBean(ItsMessageHandler.class));
            log.info("A gbt client connected: {}", socketChannel.remoteAddress());
        }
    }

    /**
     * 停止服务
     */
    public void destroy() {
        log.info("Shutdown Netty Server...");
        if(dtuChannel != null) {
            dtuChannel.close();
        }
        if( gbtChannel != null ) {
            gbtChannel.close();
        }
        workerGroup.shutdownGracefully();
        bossGroup.shutdownGracefully();
        log.info("Shutdown Netty Server Success!");
    }
}

